﻿namespace MetaProperties.Core
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq.Expressions;

    using MetaProperties.Framework;

    /// <summary>
    /// Defines a container of MetaProperties, including auto-initilization of public MetaProperties.
    /// </summary>
    public abstract class MetaPropertyHost : IMetaPropertyHost
    {
        /// <summary>
        /// The host helper.
        /// </summary>
        private readonly MetaPropertyHostHelper hostHelper;

        /// <summary>
        /// Initializes a new instance of the <see cref="MetaPropertyHost"/> class.
        /// </summary>
        public MetaPropertyHost()
            : this(true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MetaPropertyHost"/> class.
        /// </summary>
        /// <param name="initializePublicMetaProperties">if set to <c>true</c>, automatically initializes public MetaProperties.</param>
        public MetaPropertyHost(bool initializePublicMetaProperties)
        {
            this.hostHelper = new MetaPropertyHostHelper(this);

            if (initializePublicMetaProperties)
            {
                this.hostHelper.InitializePublicMetaProperties();
            }
        }

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged
        {
            add { this.hostHelper.PropertyChanged += value; }
            remove { this.hostHelper.PropertyChanged -= value; }
        }

        /// <summary>
        /// Gets all the public MetaProperties in the container.
        /// </summary>
        /// <value>The stream of public MetaProperties.</value>
        public IEnumerable<IMetaProperty> MetaProperties
        {
            get { return this.hostHelper.MetaProperties; }
        }

        /// <summary>
        /// Gets the requested MetaProperty.
        /// </summary>
        /// <param name="propertyName">The name of the property.</param>
        /// <returns>
        /// The requested MetaProperty if it exists; null otherwise.
        /// </returns>
        public IMetaProperty GetMetaProperty(string propertyName)
        {
            return this.hostHelper.GetMetaProperty(propertyName);
        }

        /// <summary>
        /// Called when [property changed].
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        public void OnPropertyChanged(string propertyName)
        {
            this.hostHelper.OnPropertyChanged(propertyName);
        }

        /// <summary>
        /// Creates a dependency between a standard property and one or more MetaProperties.
        /// When a standard property is dependent on one or more MetaProperties, its PropertyChanged
        /// event is raised whenever one of the MetaProperties value changes.
        /// </summary>
        /// <param name="targetPropertyName">Name of the target property.</param>
        /// <param name="sourceObservables">The source observables.</param>
        protected void CreateDependency(string targetPropertyName, params IObservable[] sourceObservables)
        {
            if (sourceObservables != null)
            {
                foreach (var metaProperty in sourceObservables)
                {
                    metaProperty.PropertyChanged += (s, e) => this.OnPropertyChanged(targetPropertyName);
                }
            }
        }

        /// <summary>
        /// Creates a dependency between a standard property and one or more MetaProperties.
        /// When a standard property is dependent on one or more MetaProperties, its PropertyChanged
        /// event is raised whenever one of the MetaProperties value changes.
        /// </summary>
        /// <typeparam name="TObject">The type of the object.</typeparam>
        /// <typeparam name="T">The type of the property.</typeparam>
        /// <param name="targetPropertyExpression">The property expression.</param>
        /// <param name="sourceObservables">The source observables.</param>
        protected void CreateDependency<TObject, T>(Expression<Func<TObject, T>> targetPropertyExpression, params IObservable[] sourceObservables)
        {
            if (sourceObservables != null)
            {
                string targetPropertyName = PropertyNameHelper.GetProperty(targetPropertyExpression).Name;

                foreach (var metaProperty in sourceObservables)
                {
                    metaProperty.PropertyChanged += (s, e) => this.OnPropertyChanged(targetPropertyName);
                }
            }
        }

        /// <summary>
        /// Creates a dependency between a standard property and one or more MetaProperties.
        /// When a standard property is dependent on one or more MetaProperties, its PropertyChanged
        /// event is raised whenever one of the MetaProperties value changes.
        /// </summary>
        /// <typeparam name="T">The type of the property.</typeparam>
        /// <param name="targetPropertyExpression">The property expression.</param>
        /// <param name="sourceObservables">The source observables.</param>
        protected void CreateDependency<T>(Expression<Func<T>> targetPropertyExpression, params IObservable[] sourceObservables)
        {
            if (sourceObservables != null)
            {
                string targetPropertyName = PropertyNameHelper.GetProperty(targetPropertyExpression).Name;

                foreach (var metaProperty in sourceObservables)
                {
                    metaProperty.PropertyChanged += (s, e) => this.OnPropertyChanged(targetPropertyName);
                }
            }
        }
    }
}
